<template>
  <div><canvas id="canvas"></canvas></div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
import { RenderPass, EffectComposer, OutlinePass } from "three-outlinepass";
import * as dat from "dat.gui";
import { onMounted } from "vue";
export default {
  setup() {
    let gui;
    let timer;
    let scene, camera, renderer;
    const scale = 0.001;
    let moveMixer;
    let mixer;
    let controls;
    let compose;
    let labelRenderer;
    let label;
    let clock = new THREE.Clock();
    const vector = new THREE.Vector3(); //三维坐标对象
    //相机位置
    const cameraX = 0;
    const cameraY = -8;
    const cameraZ = 8;
    const canvasWidth = window.innerWidth;
    const canvasHeight = window.innerHeight;
    let setIntervalAlarm;
    let setTimeoutAlarm;
    let state = {
      showEquipmentAlarm: false,
      equipmentSelected: {
        name: "",
        position: {},
      },
    };

    const equipmentNameList = [
      "pasted__extrudedSurface2",
      "pasted__extrudedSurface8",
      "pasted__group59_pCylinder158",
      "pasted__pCube70",
      "pasted__pCube97",
      "polySurface152",
      "polySurface156",
      "polySurface230",
      "polySurface258",
    ];

    let controlsGui = {
      材质: () => {
        if (scene.getObjectByName("turbine")) {
          let Turbine = scene.getObjectByName("turbine");
          if (Turbine.getObjectByName("颜色材质")) {
            let removeTurbine = Turbine.getObjectByName("颜色材质");
            Turbine.remove(removeTurbine);
          } else {
            loadTurbine();
          }
        }
      },
      仅部件: false,
      部件损坏告警: () => {
        state.showEquipmentAlarm = !state.showEquipmentAlarm;
        createAlarm();
      },
      风速: 5,
    };

    //初始化渲染器
    const initRenderer = () => {
      const canvas = document.getElementById("canvas");
      renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
      renderer.setSize(canvasWidth, canvasHeight);
      renderer.shadowMap.enabled = true;
      // renderer.outputEncoding = THREE.sRGBEncoding; //渲染模型
      document.body.appendChild(renderer.domElement);

      labelRenderer = new CSS2DRenderer();
      labelRenderer.setSize(window.innerWidth, window.innerHeight);
      labelRenderer.domElement.style.position = "absolute";
      // 相对鼠标的相对偏移
      labelRenderer.domElement.style.top = "-16px";
      labelRenderer.domElement.style.left = "0px";
      // //设置.pointerEvents=none，以免模型标签HTML元素遮挡鼠标选择场景模型
      labelRenderer.domElement.style.pointerEvents = "none";
      document.body.appendChild(labelRenderer.domElement);
    };

    //初始化场景
    const initScence = () => {
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0xe0e0e0);
      scene.toJSON();
      scene.fog = new THREE.Fog(0xe0e0e0, 20, 100); //定义每个物体的雾的类型
    };

    //初始化灯光
    const initLight = () => {
      scene.add(new THREE.AmbientLight(0xffffff, 1));
      const arr = [
        [100, 100, 100],
        [-100, 100, 100],
        [100, -100, 100],
      ];
      arr.forEach((lightArr) => {
        let directionalLight = new THREE.DirectionalLight(0xffffff, 3);
        let [x, y, z] = lightArr;
        directionalLight.position.set(x, y, z);
        scene.add(directionalLight);
      });
    };

    const loadTurbine = () => {
      const loader = new GLTFLoader();
      if (scene.getObjectByName("turbine")) {
        let removeTurbine = scene.getObjectByName("turbine");
        scene.remove(removeTurbine);
      }
      loader.load(
        "3d-gltf-model/turbine.glb",
        (gltf) => {
          let model = gltf.scene;
          model.name = "turbine";
          model.scale.set(scale, scale, scale);
          model.position.y = -10;
          model.rotation.y = Math.PI / 4;
          scene.add(model);
          //创建动画
          createGUI(gltf, gltf.animations);
        },
        (xhr) => {
          console.log(
            "加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%"
          );
        }
      );
    };

    //初始化物体
    const initMeshes = () => {
      // //添加平面
      // const plane = new THREE.Mesh(
      //   new THREE.PlaneGeometry(200, 200),
      //   new THREE.MeshStandardMaterial({
      //     color: 0x999999,
      //     depthWrite: false,
      //   })
      // );
      // plane.position.y = -10;
      // plane.rotation.x = -Math.PI / 2;
      // plane.receiveShadow = true;
      // scene.add(plane);

      //引入gltf模型 风机
      loadTurbine();

      //引入gltf模型 电机
      const loader = new GLTFLoader();
      loader.load(
        "3d-gltf-model/equipment.glb",
        (gltf) => {
          let model = gltf.scene;
          scene.add(model);
          model.scale.set(scale, scale, scale);
          model.position.y = -10;
          model.rotation.y = Math.PI / 4;
          model.name = "equipment";
        },
        (xhr) => {
          console.log(
            "加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%"
          );
        }
      );
    };

    //创建动画
    const createGUI = (gltf, animations) => {
      let model = gltf.scene;
      const animationClip = animations[0];
      animationClip.tracks[0].times = gltf.animations[0].tracks[0].times.map(
        (a) => a / (controlsGui.风速 / 5)
      ); // 控制有材质的风机动画速度
      animationClip.tracks[1].times = gltf.animations[0].tracks[1].times.map(
        (a) => a / (controlsGui.风速 / 5)
      ); // 控制透明的风机动画速度
      animationClip.duration = 6 / (controlsGui.风速 / 5);
      mixer = new THREE.AnimationMixer(model);
      const action = mixer.clipAction(animationClip);
      action.play();
    };

    //初始化相机
    const initCamera = () => {
      camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.1,
        100000
      );
      camera.position.set(cameraX, cameraY, cameraZ);
    };

    //初始化控制器
    const initControls = () => {
      controls = new OrbitControls(camera, renderer.domElement);
      controls.maxDistance = 20; //设置缩放范围
      controls.maxPolarAngle = Math.PI / 1.2; //设置旋转范围
    };

    //渲染场景
    const animate = () => {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
      labelRenderer.render(scene, camera);
      const delta = clock.getDelta();
      if (compose) compose.render();
      if (mixer) mixer.update(delta);
      if (moveMixer) moveMixer.update(delta);
    };

    const loadGui = () => {
      gui = new dat.GUI().addFolder("3D风机控件");
      gui.add(controlsGui, "材质");
      gui.add(controlsGui, "风速", 1, 30, 1).onChange((e) => {
        // 改变风机转速部分，必须使用防抖或者节流
        clearTimeout(timer);
        timer = setTimeout(() => {
          loadTurbine();
        }, 1000);
      });
      gui.add(controlsGui, "仅部件").onChange((val) => {
        if (val) {
          const canvas = document.getElementById("canvas");
          canvas.addEventListener("click", pointerLockControls);
          let removeTurbine = scene.getObjectByName("turbine");
          scene.remove(removeTurbine);
        } else {
          const canvas = document.getElementById("canvas");
          canvas.removeEventListener("click", pointerLockControls);
          restUnSelected();
          loadTurbine();
        }
      });
      gui.add(controlsGui, "部件损坏告警");
      gui.open();
    };

    const createAlarm = () => {
      // 设备中每个部件随机变红，显示坏掉的警告
      if (state.showEquipmentAlarm) {
        const random = parseInt(Math.random() * 9);
        if (scene.getObjectByName("equipment")) {
          let model = scene.getObjectByName("equipment");
          //不同部件材质对象采用不同变量存储
          setIntervalAlarm = setInterval(() => {
            model.traverse((children) => {
              if (children.isMesh) {
                children.material = children.material.clone();
                children.material.emissive.setHex(0);
              }
              if (children.name === equipmentNameList[random]) {
                children.material.emissive.setHex(0xff0000);
                setTimeoutAlarm = setTimeout(() => {
                  children.material.emissive.setHex(
                    0 // 变成红色后设置一个定时器，过了四秒恢复设备原有颜色
                  );
                }, 1000);
              }
            });
          }, 2000);
        }
      } else {
        clearInterval(setIntervalAlarm);
        clearTimeout(setTimeoutAlarm);
        let model = scene.getObjectByName("equipment");
        model.traverse((children) => {
          if (children.isMesh) {
            children.material = children.material.clone();
            children.material.emissive.setHex(0);
          }
        });
      }
    };

    //相机移动动画
    const moveAnimation = (mesh, x, y, z) => {
      const positionKF = new THREE.VectorKeyframeTrack(
        ".position",
        [0, 1],
        [mesh.position.x, mesh.position.y, mesh.position.z, x, y, z]
      );
      const clip = new THREE.AnimationClip("MoveAction", 1, [positionKF]);
      moveMixer = new THREE.AnimationMixer(mesh);
      const action = moveMixer.clipAction(clip);
      action.setLoop(THREE.LoopOnce);
      action.clampWhenFinished = true; //取消动画重置
      action.play();
    };

    //开启仅部件模式
    const pointerLockControls = (event) => {
      //获取三维坐标
      vector.set(
        (event.offsetX / canvasWidth) * 2 - 1,
        -(event.offsetY / canvasHeight) * 2 + 1,
        0.5
      );
      let raycaster = new THREE.Raycaster();
      raycaster.setFromCamera(vector, camera);
      let intersects = raycaster.intersectObjects(scene.children);
      if (intersects.length > 0) {
        let selected = intersects[0].object; //取第一个物体
        outline([selected]);

        let ismove =
          state.equipmentSelected.name != selected.name ||
          state.equipmentSelected.position.y == selected.position.y;

        restUnSelected();

        // 保存所选中的物体
        //一定要用深拷贝
        state.equipmentSelected.name = selected.name;
        state.equipmentSelected.position = JSON.parse(
          JSON.stringify(selected.position)
        );
        // 移动展示物体
        if (ismove) {
          updateLabal(intersects[0]);
          moveAnimation(
            selected,
            selected.position.x,
            selected.position.y + 1 / scale,
            selected.position.z
          );
        }
      }
    };

    //复原未选中物体
    const restUnSelected = () => {
      if (state.equipmentSelected.name != "") {
        if (moveMixer) moveMixer.stopAllAction();
        let equipment = scene.getObjectByName(state.equipmentSelected.name);
        equipment.position.y = state.equipmentSelected.position.y;
        outline([]);
        label.visible = false;
      }
    };

    const outline = (selectedObjects, color = 0x15c5e8) => {
      compose = new EffectComposer(renderer);
      let renderPass = new RenderPass(scene, camera);
      let outlinePass = new OutlinePass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        scene,
        camera,
        selectedObjects
      );
      outlinePass.renderToScreen = true;
      outlinePass.selectedObjects = selectedObjects;

      compose.addPass(renderPass);
      compose.addPass(outlinePass);
      let params = {
        edgeStrength: 3, //粗
        edgeGlow: 0, //发光
        edgeThickness: 20, //光晕粗
        pulsePeriod: 1, //闪烁
        usePatternTexture: false,
      };

      outlinePass.edgeStrength = params.edgeStrength;
      outlinePass.edgeGlow = params.edgeGlow;
      outlinePass.visibleEdgeColor.set(color);
      outlinePass.hiddenEdgeColor.set(color);

      compose.render(scene, camera);
    };

    /**
     * intersect参数是鼠标点击的部件
     * DOM 元素被包装到 CSS2DObject 的实例中并添加到scene中
     */
    const updateLabal = (intersect) => {
      label.position.set(
        intersect.point.x - 0.01,
        intersect.point.y + 1,
        intersect.point.z - 0.03
      );
      label.visible = true;
    };

    /**
     * 组件挂载时，创建作为label的2D对象
     * DOM 元素被包装到 CSS2DObject 的实例中并添加到scene中
     */
    const createTurbineLabel = () => {
      let div = document.createElement("div");
      div.style.visibility = "auto";
      div.innerHTML = "info\nxxxxxxxxxxxxxxxxxxxxxx";
      div.style.padding = "4px 10px";
      div.style.color = "#fff";
      div.style.fontSize = "16px";
      div.style.position = "absolute";
      div.style.backgroundColor = "rgba(25,25,25,0.5)";
      div.style.borderRadius = "5px";
      // div元素包装成为css2模型对象CSS2DObject
      label = new CSS2DObject(div);
      div.style.pointerEvents = "none"; //避免HTML标签遮挡三维场景的鼠标事件
      // 设置HTML元素标签在three.js世界坐标中位置
      // label.position.set(0, 0, 0);
      label.visible = false;
      scene.add(label);
    };

    onMounted(() => {
      initRenderer();
      initScence();
      initLight();
      initMeshes();
      initCamera();
      initControls();
      createTurbineLabel();
      animate();
      loadGui();
    });
  },
};
</script>
